#pragma once

// Creates cue sheet for current toc.
// cueSheetLen: filled with length of cue sheet in bytes
// return: newly allocated cue sheet buffer or 'NULL' on error
unsigned char *GenericMMC::createCueSheet(unsigned long variant, long *cueSheetLen)
{
	const Track *t;
	int trackNr;
	Msf start, end, index;
	unsigned char *cueSheet;
	long len = 3; // entries for lead-in, gap, lead-out
	long n; // index into cue sheet
	unsigned char ctl; // control nibbles of cue sheet entry CTL/ADR
	long i;
	unsigned char dataMode;
	int trackOffset = 0;
	long lbaOffset = 0;

	if (!diskInfo_.empty && diskInfo_.append) 
	{
		trackOffset = diskInfo_.lastTrackNr;
		lbaOffset = diskInfo_.thisSessionLba;
	}

	TrackIterator itr(toc_);

	if (itr.first(start, end) == NULL) 
	{
		return NULL;
	}

	if (toc_->catalogValid()) 
	{
		len += 2;
	}

	for (t = itr.first(start, end), trackNr = 1; t != NULL; t = itr.next(start, end), trackNr++) 
	{
		len += 1; // entry for track
		if (t->start().lba() != 0 && trackNr > 1)
		{
		  len += 1; // entry for pre-gap
		}

		if (t->type() == TrackData::AUDIO && t->isrcValid())
		{
			len += 2; // entries for ISRC code
		}
		
		len += t->nofIndices(); // entry for each index increment
	}

	cueSheet = new (unsigned char)[len * 8];
	n = 0;

	if (toc_->leadInMode() == TrackData::AUDIO) 
	{
		ctl = 0;
	}
	else 
	{
		ctl = 0x40;
	}

	if (toc_->catalogValid()) 
	{
		// fill catalog number entry
		cueSheet[n*8] = 0x02 | ctl;
		cueSheet[(n+1)*8] = 0x02 | ctl;

		for (i = 1; i <= 13; i++) 
		{
			if (i < 8) 
			{
				cueSheet[n*8 + i] = toc_->catalog(i-1) + '0';
			}
			else 
			{
				cueSheet[(n+1)*8 + i - 7] = toc_->catalog(i-1) + '0';
			}
		}
		
		cueSheet[(n+1)*8+7] = 0;
		n += 2;
	}

	// entry for lead-in
	cueSheet[n*8] = 0x01 | ctl; // CTL/ADR
	cueSheet[n*8+1] = 0;    // Track number
	cueSheet[n*8+2] = 0;    // Index

	if (cdTextEncoder_ != NULL) 
	{
		cueSheet[n*8+3] = 0x41; // Data Form: CD-DA with P-W sub-channels,
                            // main channel data generated by device
	}
	else 
	{
		cueSheet[n*8+3] = leadInOutDataMode(toc_->leadInMode());
	}

	cueSheet[n*8+4] = 0;    // Serial Copy Management System

	if (cdTextEncoder_ != NULL && (variant & CUE_VAR_CDTEXT_NO_TIME) == 0) 
	{
		cueSheet[n*8+5] = leadInStart_.min();
		cueSheet[n*8+6] = leadInStart_.sec();
		cueSheet[n*8+7] = leadInStart_.frac();
	}
	else 
	{
		cueSheet[n*8+5] = 0;    // MIN
		cueSheet[n*8+6] = 0;    // SEC
		cueSheet[n*8+7] = 0;    // FRAME
	}

	n++;

	int firstTrack = 1;
	for (t = itr.first(start, end), trackNr = trackOffset + 1; t != NULL; t = itr.next(start, end), trackNr++) 
	{
		if (encodingMode_ == 0) 
		{
			// just used for some experiments with raw writing
			dataMode = 0;
		}
		else 
		{
			switch (t->type()) 
			{
			case TrackData::AUDIO:
				dataMode = 0;
				break;
			case TrackData::MODE1:
			case TrackData::MODE1_RAW:
				dataMode = 0x10;
				break;
			case TrackData::MODE2:
				dataMode = 0x30;
				break;
			case TrackData::MODE2_RAW: // assume it contains XA sectors
			case TrackData::MODE2_FORM1:
			case TrackData::MODE2_FORM2:
			case TrackData::MODE2_FORM_MIX:
				dataMode = 0x20;
			break;
			default:
				dataMode = 0;
			break;
			}
		} // if

		// add mode for sub-channel writing
		dataMode |= subChannelDataForm(t->subChannelType(), subChannelEncodingMode(t->subChannelType()));

		ctl = 0;
		if (t->copyPermitted()) 
		{
		  ctl |= 0x20;
		}

		if (t->type() == TrackData::AUDIO) 
		{
			// audio track
			if (t->preEmphasis()) 
			{
				ctl |= 0x10;
			}
			
			if (t->audioType() == 1) 
			{
				ctl |= 0x80;
			}

			if (t->isrcValid()) 
			{
				if ((variant & CUE_VAR_ISRC_NO_CTL) == 0)
					cueSheet[n*8] = ctl | 0x03;
				else
					cueSheet[n*8] = 0x03;

				cueSheet[n*8+1] = trackNr;
				cueSheet[n*8+2] = t->isrcCountry(0);
				cueSheet[n*8+3] = t->isrcCountry(1);
				cueSheet[n*8+4] = t->isrcOwner(0);  
				cueSheet[n*8+5] = t->isrcOwner(1);   
				cueSheet[n*8+6] = t->isrcOwner(2);  
				cueSheet[n*8+7] = t->isrcYear(0) + '0';
				n++;

				if ((variant & CUE_VAR_ISRC_NO_CTL) == 0)
				  cueSheet[n*8] = ctl | 0x03;
				else
				  cueSheet[n*8] = 0x03;

				cueSheet[n*8+1] = trackNr;
				cueSheet[n*8+2] = t->isrcYear(1) + '0';
				cueSheet[n*8+3] = t->isrcSerial(0) + '0';
				cueSheet[n*8+4] = t->isrcSerial(1) + '0';
				cueSheet[n*8+5] = t->isrcSerial(2) + '0';
				cueSheet[n*8+6] = t->isrcSerial(3) + '0';
				cueSheet[n*8+7] = t->isrcSerial(4) + '0';
				n++;
			}
		}
		else 
		{
		  // data track
		  ctl |= 0x40;
		}
		 
		if (firstTrack) 
		{
			Msf sessionStart(lbaOffset);

			// entry for gap before first track
			cueSheet[n*8]   = ctl | 0x01;
			cueSheet[n*8+1] = trackNr;
			cueSheet[n*8+2] = 0;    // Index 0
			cueSheet[n*8+3] = dataMode;    // Data Form
			cueSheet[n*8+4] = 0;    // Serial Copy Management System
			cueSheet[n*8+5] = sessionStart.min();
			cueSheet[n*8+6] = sessionStart.sec();
			cueSheet[n*8+7] = sessionStart.frac();
			n++;
		}
		else if (t->start().lba() != 0) 
		{
			// entry for pre-gap
			// lbaOffset is 0 
			Msf pstart(lbaOffset + start.lba() - t->start().lba() + 150);

			cueSheet[n*8]   = ctl | 0x01;
			cueSheet[n*8+1] = trackNr;
			cueSheet[n*8+2] = 0; // Index 0 indicates pre-gap
			cueSheet[n*8+3] = dataMode; // Data Form
			cueSheet[n*8+4] = 0; // no alternate copy bit
			cueSheet[n*8+5] = pstart.min();
			cueSheet[n*8+6] = pstart.sec();
			cueSheet[n*8+7] = pstart.frac();
			n++;
		}

		Msf tstart(lbaOffset + start.lba() + 150);
    
		cueSheet[n*8]   = ctl | 0x01;
		cueSheet[n*8+1] = trackNr;
		cueSheet[n*8+2] = 1; // Index 1
		cueSheet[n*8+3] = dataMode; // Data Form
		cueSheet[n*8+4] = 0; // no alternate copy bit
		cueSheet[n*8+5] = tstart.min();
		cueSheet[n*8+6] = tstart.sec();
		cueSheet[n*8+7] = tstart.frac();
		n++;

		for (i = 0; i < t->nofIndices(); i++) 
		{
		  index = tstart + t->getIndex(i);
		  cueSheet[n*8]   = ctl | 0x01;
		  cueSheet[n*8+1] = trackNr;
		  cueSheet[n*8+2] = i+2; // Index
		  cueSheet[n*8+3] = dataMode; // Data Form
		  cueSheet[n*8+4] = 0; // no alternate copy bit
		  cueSheet[n*8+5] = index.min();
		  cueSheet[n*8+6] = index.sec();
		  cueSheet[n*8+7] = index.frac();
		  n++;
		}

		firstTrack = 0;
	}

	assert(n == len - 1);

	// entry for lead out
	Msf lostart(lbaOffset + toc_->length().lba() + 150);
	ctl = toc_->leadOutMode() == TrackData::AUDIO ? 0 : 0x40;

	cueSheet[n*8]   = ctl | 0x01;
	cueSheet[n*8+1] = 0xaa;
	cueSheet[n*8+2] = 1; // Index 1
	cueSheet[n*8+3] = leadInOutDataMode(toc_->leadOutMode());
	cueSheet[n*8+4] = 0; // no alternate copy bit
	cueSheet[n*8+5] = lostart.min();
	cueSheet[n*8+6] = lostart.sec();
	cueSheet[n*8+7] = lostart.frac();

	message(3, "\nCue Sheet (variant %lx):", variant);
	message(3, "CTL/  TNO  INDEX  DATA  SCMS  MIN  SEC  FRAME");
	message(3, "ADR               FORM");

	for (n = 0; n < len; n++) 
	{
		message(3, "%02x    %02x    %02x     %02x    %02x   %02d   %02d   %02d",
		cueSheet[n*8],
		cueSheet[n*8+1], cueSheet[n*8+2], cueSheet[n*8+3], cueSheet[n*8+4],
		cueSheet[n*8+5], cueSheet[n*8+6], cueSheet[n*8+7]);
	}

	*cueSheetLen = len * 8;

	return cueSheet;
}

int CDiscWriter::WriteDisc()
{
	long nLength;
	m_scsi.ReadDiscCapacity(GetCurrentDevice(), nLength);

	if ((long)dSize> nLength)
	{
		return FAIL;
	}
	
	// check if the cd is writeable
	long nDiscType = discCDR, nDiscStatus = InfoEmpty;
	int iError = m_scsi.ReadDiscInformation(GetCurrentDevice(), &nDiscType, &nDiscStatus, 0, 0);
	if ((nDiscStatus != InfoEmpty && nDiscStatus != InfoOpen) || iError != 0)
	{
		return FAIL;
	}

	// do power calibration
	m_scsi.PerformOPC(GetCurrentDevice());

	// Allocate our buffer for read and write. We make a buffer that is 16 blocks
	// long. The block length is different between audio and data cds.
	long blockCount = 16;
	BYTE* pBuffer = new BYTE[m_discToWrite->GetBlockSize() * blockCount];
	memset(pBuffer, 0, m_discToWrite->GetBlockSize() * blockCount);

	for (int n=0;n<m_discToWrite->GetSessionCount();n++)
	{		
		if (SendWriteModePage() == false)
			break;
		
		for (m_nCurTrack=0;m_nCurTrack<m_discToWrite->GetTrackCount();m_nCurTrack++)
		{
			nNextWriteAddr = GetNextWriteAddress();
			if (nNextWriteAddr == -1)
			{
				ChangeStatus("The write address for the next track was not returned by the device", statusErrorAddr, statusError);
				break;
			}
						
			m_nCurLBA = nNextWriteAddr;

	 		long lRead = m_discToWrite->GetBlockSize() * blockCount; // blockCount blocks
			while (m_discToWrite->Read(pBuffer, lRead) == true)
			{				
				int iError = m_scsi.WriteBlock(GetCurrentDevice(), pBuffer, blockCount, lRead, m_nCurLBA);
				if (iError != 0)
				{
	 				break;
				}

				m_nCurLBA += blockCount;
			}
	 		
			m_scsi.SynchronizeCache(GetCurrentDevice());
			m_scsi.CloseTrackOrSession(GetCurrentDevice(), false, 0xFF);
	 
		}
		m_scsi.CloseTrackOrSession(GetCurrentDevice(), true, 0);
	}

	return 0;
}